﻿//#define VERBOSE
using System;
using RayDen.Library.Components.SystemComponents;
using RayDen.Library.Core;
using RayDen.Library.Core.Components;
using RayDen.Library.Core.Primitives;

namespace RayDen.Library.Components.Surface
{
    [Serializable]
    public class OrenNayarMaterial : SurfaceBsdf
    {
        public override BrdfType Type
        {
            get { return BrdfType.Diffuse; }
        }

        public OrenNayarMaterial(RgbSpectrum reflectance, float sig)
        {
            R = reflectance;
            float sigma = MathLab.Radians(sig*60f);
            float sigma2 = sigma * sigma;
            A = 1f - (sigma2 / (2f * (sigma2 + 0.33f)));
            B = 0.45f * sigma2 / (sigma2 + 0.09f);
        }

        protected override void EvalParams(ref SurfaceTextureData surfaceData)
        {
            R = surfaceData.Diffuse;
            
            if (!surfaceData.Roughness.IsBlack())
            {
                float sigma = MathLab.Radians(6f * surfaceData.Roughness.Filter());
                float sigma2 = sigma*sigma;
                A = 1f - (sigma2/(2f*(sigma2 + 0.33f)));
                B = 0.45f*sigma2/(sigma2 + 0.09f);
            }
        }

        public override bool IsDiffuse() { return true; }
        public override bool IsSpecular() { return false; }

        public override void f(ref Vector wo, ref Vector wi, ref Normal N, ref RgbSpectrum in_fs, out RgbSpectrum fs)
        {
            CreateFrame(ref N);
            var lwo = WorldToLocal(ref wo).Normalize();
            var lwi = WorldToLocal(ref wi).Normalize();
#if VERBOSE
            Assert.IsNotNaN(ref lwo, "LWO "+wo.ToString());
            Assert.IsNotNaN(ref lwi, "LWI "+ wi.ToString());
#endif

            EvalBrdf(out fs, ref lwi, ref lwo);
           
        }

        private void EvalBrdf(out RgbSpectrum fs, ref Vector lwi, ref Vector lwo)
        {
            float sinthetai = SinTheta(ref lwi);
            float sinthetao = SinTheta(ref lwo);
            // Compute cosine term of Oren--Nayar model
            float sinphii = SinPhi(ref lwi), cosphii = CosPhi(ref lwi);
            float sinphio = SinPhi(ref lwo), cosphio = CosPhi(ref lwo);
            float dcos = cosphii*cosphio + sinphii*sinphio;
#if VERBOSE
            Assert.IsNotNaN(cosphii,"cosphii "+lwi.ToString());
            Assert.IsNotNaN(cosphio, "cosphio " + lwo.ToString());
            Assert.IsNotNaN(sinphii, "sinphii " + lwi.ToString());
            Assert.IsNotNaN(sinphio, "sinphio " + lwo.ToString());
#endif
            float maxcos = Math.Max(0f, dcos);
            // Compute sine and tangent terms of Oren--Nayar model
            float sinalpha, tanbeta;
            if (Math.Abs(CosTheta(ref lwi)) > Math.Abs(CosTheta(ref lwo)))
            {
                sinalpha = sinthetao;
                tanbeta = sinthetai/Math.Abs(CosTheta(ref lwi));
            }
            else
            {
                sinalpha = sinthetai;
                tanbeta = sinthetao/Math.Abs(CosTheta(ref lwo));
            }
            

            fs = R*MathLab.INVPI*
                 (A + B*maxcos*sinalpha*tanbeta);
#if VERBOSE
            if (fs.IsNegative())
            {
                Tracer.TraceLine("OrenNayar - f - negative");
            }
            if (fs.IsBlack())
            {
                Tracer.TraceLine("OrenNayar - f - black");
            }
#endif

        }

        public override void Sample_f(ref Vector wo, ref Normal N, ref Normal shadeN, ref RgbSpectrum in_f, float u0, float u1, float u2, ref SurfaceTextureData surfaceData, out BsdfSampleData result)
        {
            result.Lambda = 0f;

            EvalParams(ref surfaceData);
            CreateFrame(ref shadeN);
            Vector dir =
                MC.CosineSampleHemisphere(u0, u1).Normalize();
            result.Pdf = dir.z * MathLab.INVPI;
            result.Type = BrdfType.Diffuse;

            //Vector v1, v2;
            //Normal n = N;
            //Vector.CoordinateSystem(ref n, out v1, out v2);
            var wi = LocalToWorld(ref dir);

                /*new Vector(
                v1.x * dir.x + v2.x * dir.y + n.x * dir.z,
                v1.y * dir.x + v2.y * dir.y + n.y * dir.z,
                v1.z * dir.x + v2.z * dir.y + n.z * dir.z);*/


            float dp = (Normal.Dot(ref shadeN, ref wi));
            // Using 0.01 instead of 0.0 to cut down fireflies
            if (dp <= 0.0001f)
            {
                result.Pdf = 0f;
                result.F =  new RgbSpectrum(0f);
            }

            result.Pdf /= dp;

            RgbSpectrum fs;
            var lwo = WorldToLocal(ref wo).Normalize();
            EvalBrdf(out fs, ref dir, ref lwo);
            result.Wi =  wi;
            result.F =  fs;
        }

        RgbSpectrum R;
        float A, B;
    }
}